Skip to content

feat(python): expose table compaction through new_compact_builder - #1323

Merged
JingsongLi merged 4 commits into
apache:mainfrom
LuciferYang:feat/python-compact
Nov 5, 2026
Merged

JingsongLi merged 4 commits into
apache:mainfrom
LuciferYang:feat/python-compact

Conversation

@LuciferYang

Copy link
Copy Markdown
Contributor

Purpose

Linked issue: close #1321

Python users can compact a table only by running CALL sys.compact through SQLContext. This adds Table.new_compact_builder(), a thin wrapper over core CompactBuilder with the same plan, execute and commit flow as the procedure. Part of #286.

Brief change log

  • CompactBuilder.with_partition_filter(predicate) takes the read-filter dict format, and repeated calls combine with AND. with_commit_user(commit_user) sets the commit user. Both are validated by the core builder when called, as in GlobalIndexDropBuilder and BatchWriteBuilder.
  • execute() plans, runs every task and makes one COMPACT commit. It returns a dict with tasks and the commit's write metrics. Nothing is committed when no task is planned.
  • When a task fails, the outputs of earlier tasks are deleted. If that deletion fails, the binding emits a RuntimeWarning with the deletion error and then raises the task's error. sys.compact logs the same message with log::warn!, but the Python binding has no logger.
  • Type stubs for CompactBuilder and Table.new_compact_builder.

Tests

New test_compact.py:

  • append table: files are merged, rows are unchanged, and a COMPACT snapshot is published;
  • nothing to compact: no commit;
  • partition filter: p=a gets new files and p=b is untouched;
  • the commit user is recorded as <commit_user>-compact-<suffix>;
  • an invalid partition filter or commit user is rejected when set;
  • write-only primary-key table: two sorted runs are merged into one file;
  • deletion-vector primary-key table: ValueError and no commit without a local cache; with local-cache.enabled=true, compaction makes the rows visible.

API and Format

New Python API: Table.new_compact_builder() and CompactBuilder. No storage format change.

Documentation

New Table Maintenance > Compaction section in docs/src/python-binding.md, and a pointer to it from docs/src/compaction.md.

Generated-by: Claude Opus

yangjie01 and others added 3 commits November 3, 2026 16:28
The compaction APIs merged for apache#1247 are reachable from DataFusion's
CALL sys.compact but not from the Python binding. Expose the same
plan-execute-commit flow as a one-shot builder:

- with_partition_filter restricts planning to partitions, combining
  repeated filters with AND, like the read filters.
- with_commit_user overrides the generated commit identity.
- execute plans all tasks, executes them and submits one commit; a
  failed task aborts the files already produced so nothing
  unreferenced is left behind. No commit is made when no task is
  planned.

Tests cover merging with row preservation, the no-task no-commit
case, partition isolation and the commit identity, using write-only
tables so the explicit compaction has files to merge.

Part of apache#286
deletion is reported as a ``RuntimeWarning``.

Warning: This method blocks on a DataFusion runtime.
Calling this from an active asyncio event loop will result in a panic.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Describe the blocking behavior of execute() in asyncio

An active Python asyncio loop does not enter a Tokio runtime. I ran execute() inside asyncio.run() on a table with two compaction tasks and it completed both tasks. On that path, the synchronous call blocks the Python event-loop thread until it returns. The nested-runtime panic applies when the calling thread is already executing Tokio.

Please document the event-loop blocking behavior here so async callers know when to offload the call to a worker thread.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced the panic note. The docstring now says execute() blocks the calling thread with the GIL released, so a caller on an asyncio loop should use await asyncio.to_thread(builder.execute).

Co-Authored-By: Claude Code <noreply@anthropic.com>

@JingsongLi JingsongLi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

@JingsongLi
JingsongLi merged commit a37345b into apache:main Nov 5, 2026
15 checks passed
@LuciferYang

Copy link
Copy Markdown
Contributor Author

Thank you @JingsongLi

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Expose table compaction in the Python binding

3 participants